<?php
class Db {
	private static $instance; //保存DB的单个实例
	
	protected $dbType; //数据库类型
	protected $linkID = array(); //数据库连接池
	protected $_linkID = null; //当前数据库连接
	protected $queryID = null;
	protected $connected = false; //是否已连接数据库
	
	protected $cfg = array(); //这是整个的配置
	protected $config = array(); //这是数据库主机的配置
	protected $beginTime;
	protected $queryStr = '';
	protected $lastInsID = null;
	protected $numRows = 0;
	protected $numCols = 0;
	protected $transTimes = 0;
	protected $error = '';
	protected $comparison      = array('eq'=>'=','neq'=>'!=','gt'=>'>','egt'=>'>=','lt'=>'<','elt'=>'<=','notlike'=>'NOT LIKE','like'=>'LIKE');
	protected $selectSql  =     'SELECT%DISTINCT% %FIELDS% FROM %TABLE%%JOIN%%WHERE%%GROUP%%HAVING%%ORDER%%LIMIT%';
	
	/**
	 * 私有构造函数，防止外部实例化
	 */
	private function __construct() {
		$this->setCfg();
		
		$this->dbType = ucfirst(strtolower($this->cfg['db_type']));
		$dbClass = 'Db'. $this->dbType;
        $dbDriverPath = FRAMEWORK_QING_ROOT . '/Lib/Db/drivers/';
        $this->require_cache( $dbDriverPath . $dbClass . '.class.php');
		
		// 检查驱动类
        if(class_exists($dbClass)) {
            $db = new $dbClass();
			$db->cfg = $this->cfg;
			$db->config = $this->cfg['db_host'];
            // 获取当前的数据库类型
            if( 'pdo' != strtolower($this->cfg['db_type']) ) {
                $db->dbType = strtoupper($this->dbType);
            } else {
				$match  =  explode(':',$this->cfg['db_pdo_dsn']);
				$db->dbType = strtoupper(trim($match[0]));
			}
        }else {
            // 类没有定义
            throwException($dbClass . ' Undefined!');
        }
		//return $db; 构造函数中使用return没作用
        self::$instance = $db;
	}
	
	private function setCfg() {
		$this->cfg = DC();
	}
	
	/**
	 * 获取DB单例的方法
	 */
	public static function getInstance() {
		if(!isset(self::$instance)) {
			new self();
		}
		
		return self::$instance;
	}
	
	/**
	 * 切换数据库连接
	 */
	public function switchConnect($linkNum) {
		//只有读操作才需要切换连接
		$linkNum = 'r' . $linkNum;
		if(isset($this->linkID[$linkNum])) {
            // 存在指定的数据库连接序号
            $this->_linkID  =   $this->linkID[$linkNum];
            return true;
        }else{
            return false;
        }
	}
	
	/**
	 * 插入数据
	 * options = array('table'=>, 'lock'=>true)
	 */
	public function insert($data,$options=array()) {
        foreach ($data as $key=>$val){
            $value   =  $this->parseValue($val);
            if(is_scalar($value)) { // 过滤非标量数据
                $values[]   =  $value;
                $fields[]     =  $this->addSpecialChar($key);
            }
        }
        $sql   =  'INSERT INTO '.$this->parseTable($options['table']).' ('.implode(',', $fields).') VALUES ('.implode(',', $values).')';
        $sql   .= $this->parseLock(isset($options['lock'])?$options['lock']:false);
        return $this->execute($sql);
    }
	
	/**
	 * 通过select方式插入
	 * options = array('table'=>'', 'distinct'=>true, 'field'=>null, 'join'=>, 'where'=>, 'group'=>, 'having'=>, 'order'=>, 'limit'=>, 'lock'=>)
	 */
	public function selectInsert($fields,$table,$options=array()) {
        if(is_string($fields))   $fields    = explode(',',$fields);
        array_walk($fields, array($this, 'addSpecialChar'));
        $sql   =    'INSERT INTO '.$this->parseTable($table).' ('.implode(',', $fields).') ';
        $sql  .= str_replace(
            array('%TABLE%','%DISTINCT%','%FIELDS%','%JOIN%','%WHERE%','%GROUP%','%HAVING%','%ORDER%','%LIMIT%'),
            array(
                $this->parseTable($options['table']),
                $this->parseDistinct(isset($options['distinct'])?$options['distinct']:false),
                $this->parseField(isset($options['field'])?$options['field']:'*'),
                $this->parseJoin(isset($options['join'])?$options['join']:''),
                $this->parseWhere(isset($options['where'])?$options['where']:''),
                $this->parseGroup(isset($options['group'])?$options['group']:''),
                $this->parseHaving(isset($options['having'])?$options['having']:''),
                $this->parseOrder(isset($options['order'])?$options['order']:''),
                $this->parseLimit(isset($options['limit'])?$options['limit']:'')
            ),$this->selectSql);
        $sql   .= $this->parseLock(isset($options['lock'])?$options['lock']:false);
        return $this->execute($sql);
    }
	
	/**
	 * 更新记录
	 * options = array('table'=>, 'where'=>, 'order'=>, 'limit'=>, 'lock'=>)
	 */
	public function update($data,$options) {
        $sql   = 'UPDATE '
            .$this->parseTable($options['table'])
            .$this->parseSet($data)
            .$this->parseWhere(isset($options['where'])?$options['where']:'')
            .$this->parseOrder(isset($options['order'])?$options['order']:'')
            .$this->parseLimit(isset($options['limit'])?$options['limit']:'')
            .$this->parseLock(isset($options['lock'])?$options['lock']:false);
        return $this->execute($sql);
    }
	
	/**
	 * 删除数据
	 * options = array('table'=>, 'where'=>, 'order'=>, 'limit'=>, 'lock'=>)
	 */
	public function delete($options=array())
    {
        $sql   = 'DELETE FROM '
            .$this->parseTable($options['table'])
            .$this->parseWhere(isset($options['where'])?$options['where']:'')
            .$this->parseOrder(isset($options['order'])?$options['order']:'')
            .$this->parseLimit(isset($options['limit'])?$options['limit']:'')
            .$this->parseLock(isset($options['lock'])?$options['lock']:false);
        return $this->execute($sql);
    }
	
	/**
	 * 查找记录
	 * options = array('page'=>, 'limit'=>, 'table'=>, 'distinct'=>, 'field'=>, 'join'=>, 'where'=>, 'group'=>, 'having'=>, 'order'=>, 'lock'=>)
	 */
	public function select($options=array()) {
        if(isset($options['page'])) {
            // 根据页数计算limit
            list($page,$listRows) =  explode(',',$options['page']);
            $listRows = $listRows?$listRows:((isset($options['limit']) && is_numeric($options['limit']))?$options['limit']:20);
            $offset  =  $listRows*((int)$page-1);
            $options['limit'] =  $offset.','.$listRows;
        }
        $sql   = str_replace(
            array('%TABLE%','%DISTINCT%','%FIELDS%','%JOIN%','%WHERE%','%GROUP%','%HAVING%','%ORDER%','%LIMIT%'),
            array(
                $this->parseTable($options['table']),
                $this->parseDistinct(isset($options['distinct'])?$options['distinct']:false),
                $this->parseField(isset($options['field'])?$options['field']:'*'),
                $this->parseJoin(isset($options['join'])?$options['join']:''),
                $this->parseWhere(isset($options['where'])?$options['where']:''),
                $this->parseGroup(isset($options['group'])?$options['group']:''),
                $this->parseHaving(isset($options['having'])?$options['having']:''),
                $this->parseOrder(isset($options['order'])?$options['order']:''),
                $this->parseLimit(isset($options['limit'])?$options['limit']:'')
            ),$this->selectSql);
        $sql   .= $this->parseLock(isset($options['lock'])?$options['lock']:false);
        return $this->query($sql);
    }
	
	/**
	 * 设置/获取查询次数
	 */
	public function Q($times='') {
        static $_times = 0;
        if(empty($times)) {
            return $_times;
        }else{
            $_times++;
            // 记录开始执行时间
            $this->beginTime = microtime(TRUE);
        }
    }
	
	/**
	 * 设置/获取写入次数
	 */
	public function W($times='') {
        static $_times = 0;
        if(empty($times)) {
            return $_times;
        }else{
            $_times++;
            // 记录开始执行时间
            $this->beginTime = microtime(TRUE);
        }
    }
	
	/**
	 * 获取最后一次查询的sql
	 */
	public function getLastSql() {
        return $this->queryStr;
    }
	
	/**
	 * 获取最近错误信息
	 */
	public function getError() {
        return $this->error;
    }
	
	/**
	 * 获取数据库操作对象，用于实现最底层的操作
	 */
	public function getDbHandle($read = true) {
		$this->initConnect($read);
		
		return $this->_linkID;
	}
	
	/**
	 * 初始化数据库连接
	 */
	protected function initConnect($read=true) {
		// 主从式采用读写分离
		if(!$read) {
			// 默认主服务器是连接第一个数据库配置
			$r  =   0;
			$config = $this->config['w'];
		} else {
			// 读操作连接从服务器
			$r = floor(mt_rand(0,count($this->config['r'])-1));   // 每次随机连接的数据库
			$config = $this->config['r'][$r];
		}
		
        $this->_linkID = $this->connect($config, $r, $read);
	}
	
	/**
	 * 调试，记录sql
	 */
	protected function debug() {
        // 记录操作结束时间
        if ($this->cfg['db_debug'])    {
            //具体记录sql操作
        }
    }
	
	/**
	 * 设置锁机制
	 */
	protected function parseLock($lock=false) {
        if(!$lock) return '';
        if('ORACLE' == $this->dbType) {
            return ' FOR UPDATE NOWAIT ';
        }
        return ' FOR UPDATE ';
    }
	
	
	/**
	 * set分析
	 * $data : array('name'=>'tom', 'age'=>14) ===> `name`='tom', `age`=14
	 */
	protected function parseSet($data) {
        foreach ($data as $key=>$val){
            $value   =  $this->parseValue($val);
            if(is_scalar($value)) // 过滤非标量数据
                $set[]    = $this->addSpecialChar($key).'='.$value;
        }
        return ' SET '.implode(',',$set);
    }
	
	/**
	 * value分析
	 * 'tom' ===> 'tom'
	 * array('exp', '`age`+1') ===> `age`+1
	 * null ===> null
	 */
	protected function parseValue(&$value) {
        if(is_string($value)) {
            $value = '\''.$this->escape_string($value).'\'';
        }elseif(isset($value[0]) && is_string($value[0]) && strtolower($value[0]) == 'exp'){
            $value   =  $this->escape_string($value[1]);
        }elseif(is_null($value)){
            $value   =  'null';
        }
        return $value;
    }
	
	/**
	 * field分析
	 * array('age' => 'myage') ===> `age` AS `myage`
	 * array('age', 'name') ===> `age`, `name`
	 * 'age' ===> `age`
	 * '' ===> *
	 */
	protected function parseField($fields) {
        if(is_array($fields)) {
            // 完善数组方式传字段名的支持
            // 支持 'field1'=>'field2' 这样的字段别名定义
            $array   =  array();
            foreach ($fields as $key=>$field){
                if(!is_numeric($key))
                    $array[] =  $this->addSpecialChar($key).' AS '.$this->addSpecialChar($field);
                else
                    $array[] =  $this->addSpecialChar($field);
            }
            $fieldsStr = implode(',', $array);
        }elseif(is_string($fields) && !empty($fields)) {
            $fieldsStr = $this->addSpecialChar($fields);
        }else{
            $fieldsStr = '*';
        }
        return $fieldsStr;
    }
	
	/**
	 * table分析
	 * game,user/array('game', 'user') ===> `game`, `user`
	 * array('db'=>'game') ===> `db` `game`
	 */
	protected function parseTable($tables) {
        if(is_string($tables))
            $tables  =  explode(',',$tables);
        $array   =  array();
        foreach ($tables as $key=>$table){
            if(is_numeric($key)) {
                $array[] =  $this->addSpecialChar($table);
            }else{
                $array[] =  $this->addSpecialChar($key).' '.$this->addSpecialChar($table);
            }
        }
        return implode(',',$array);
    }
	
	/**
	 * where分析（很强大）
	 * `name`='tom' AND `age`=14 ===> WHERE `name`='tom' AND `age`=14
	 */
	protected function parseWhere($where) {
        $whereStr = '';
        if(is_string($where)) {
            // 直接使用字符串条件
            $whereStr = $where;
        }else{ // 使用数组条件表达式
            if(array_key_exists('_logic',$where)) {
                // 定义逻辑运算规则 例如 OR XOR AND NOT
                $operate    =   ' '.strtoupper($where['_logic']).' ';
                unset($where['_logic']);
            }else{
                // 默认进行 AND 运算
                $operate    =   ' AND ';
            }
            foreach ($where as $key=>$val){
                $whereStr .= "( ";
                if(0===strpos($key,'_')) {
                    // 解析特殊条件表达式
                    $whereStr   .= $this->parseThinkWhere($key,$val);
                }else{
                    $key = $this->addSpecialChar($key);
                    if(is_array($val)) {
                        if(is_string($val[0])) {
                            if(preg_match('/^(EQ|NEQ|GT|EGT|LT|ELT|NOTLIKE|LIKE)$/i',$val[0])) { // 比较运算
                                $whereStr .= $key.' '.$this->comparison[strtolower($val[0])].' '.$this->parseValue($val[1]);
                            }elseif('exp'==strtolower($val[0])){ // 使用表达式
                                $whereStr .= ' ('.$key.' '.$val[1].') ';
                            }elseif(preg_match('/IN/i',$val[0])){ // IN 运算
                                if(is_array($val[1])) {
                                    array_walk($val[1], array($this, 'parseValue'));
                                    $zone   =   implode(',',$val[1]);
                                }else{
                                    $zone   =   $val[1];
                                }
                                $whereStr .= $key.' '.strtoupper($val[0]).' ('.$zone.')';
                            }elseif(preg_match('/BETWEEN/i',$val[0])){ // BETWEEN运算
                                $data = is_string($val[1])? explode(',',$val[1]):$val[1];
                                $whereStr .=  ' ('.$key.' '.strtoupper($val[0]).' '.$this->parseValue($data[0]).' AND '.$this->parseValue($data[1]).' )';
                            }else{
                                throwException('EXPRESS ERROR:'.$val[0]);
                            }
                        }else {
                            $count = count($val);
                            if(in_array(strtoupper(trim($val[$count-1])),array('AND','OR','XOR'))) {
                                $rule = strtoupper(trim($val[$count-1]));
                                $count   =  $count -1;
                            }else{
                                $rule = 'AND';
                            }
                            for($i=0;$i<$count;$i++) {
                                $data = is_array($val[$i])?$val[$i][1]:$val[$i];
                                if('exp'==strtolower($val[$i][0])) {
                                    $whereStr .= '('.$key.' '.$data.') '.$rule.' ';
                                }else{
                                    $op = is_array($val[$i])?$this->comparison[strtolower($val[$i][0])]:'=';
                                    $whereStr .= '('.$key.' '.$op.' '.$this->parseValue($data).') '.$rule.' ';
                                }
                            }
                            $whereStr = substr($whereStr,0,-4);
                        }
                    }else {
                        //对字符串类型字段采用模糊匹配
                        if($this->cfg['db_like_fields'] && preg_match('/('.$this->cfg['db_like_fields'].')/i',$key)) {
                            $val  =  '%'.$val.'%';
                            $whereStr .= $key." LIKE ".$this->parseValue($val);
                        }else {
                            $whereStr .= $key." = ".$this->parseValue($val);
                        }
                    }
                }
                $whereStr .= ' )'.$operate;
            }
            $whereStr = substr($whereStr,0,-strlen($operate));
        }
        return empty($whereStr)?'':' WHERE '.$whereStr;
    }
	
	/**
	 * 特殊条件分析
	 */
	protected function parseThinkWhere($key,$val) {
        $whereStr   = '';
        switch($key) {
            case '_string':
                // 字符串模式查询条件
                $whereStr = $val;
                break;
            case '_complex':
                // 复合查询条件
                $whereStr   = substr($this->parseWhere($val),6);
                break;
            case '_query':
                // 字符串模式查询条件
                parse_str($val,$where);
                if(array_key_exists('_logic',$where)) {
                    $op   =  ' '.strtoupper($where['_logic']).' ';
                    unset($where['_logic']);
                }else{
                    $op   =  ' AND ';
                }
                $array   =  array();
                foreach ($where as $field=>$data)
                    $array[] = $this->addSpecialChar($field).' = '.$this->parseValue($data);
                $whereStr   = implode($op,$array);
                break;
        }
        return $whereStr;
    }
	
	/**
	 * limit分析
	 * 10 ===> LIMIT 10
	 * 10, 5 ===> LIMIT 10, 5
	 */
	protected function parseLimit($limit) {
        return !empty($limit)?   ' LIMIT '.$limit.' ':'';
    }
	
	/**
	 * join分析
	 */
	protected function parseJoin($join) {
        $joinStr = '';
        if(!empty($join)) {
            if(is_array($join)) {
                foreach ($join as $key=>$_join){
                    if(false !== stripos($_join,'JOIN'))
                        $joinStr .= ' '.$_join;
                    else
                        $joinStr .= ' LEFT JOIN ' .$_join;
                }
            }else{
                $joinStr .= ' LEFT JOIN ' .$join;
            }
        }
        return $joinStr;
    }
	
	/**
	 * order分析
	 * `age` ===> ORDER BY `age`
	 * array('age' => 'ASC', 'name' => 'DESC') ===> ORDER BY `age` ASC, `name` DESC
	 */
	protected function parseOrder($order) {
        if(is_array($order)) {
            $array   =  array();
            foreach ($order as $key=>$val){
                if(is_numeric($key)) {
                    $array[] =  $this->addSpecialChar($val);
                }else{
                    $array[] =  $this->addSpecialChar($key).' '.$val;
                }
            }
            $order   =  implode(',',$array);
        }
        return !empty($order)?  ' ORDER BY '.$order:'';
    }
	
	/**
	 * group分析
	 */
	protected function parseGroup($group)
    {
        return !empty($group)? ' GROUP BY '.$group:'';
    }
	
	/**
	 * having分析
	 */
	protected function parseHaving($having)
    {
        return  !empty($having)?   ' HAVING '.$having:'';
    }
	
	/**
	 * distinct分析
	 */
	protected function parseDistinct($distinct) {
        return !empty($distinct)?   ' DISTINCT ' :'';
    }
	
	/**
	 * 在表名，字段名左右加上`，针对mysql
	 */
	protected function addSpecialChar(&$value) {
        if('mysql' === strtolower($this->dbType)){
            $value   =  trim($value);
            if( false !== strpos($value,' ') || false !== strpos($value,',') || false !== strpos($value,'*') ||  false !== strpos($value,'(') || false !== strpos($value,'.') || false !== strpos($value,'`')) {
                //如果包含* 或者 使用了sql方法 则不作处理
            }else{
                $value = '`'.$value.'`';
            }
        }
        return $value;
    }
	
	/**
	 * 优化的require_once
	 */
	private function require_cache($filename)
	{
		static $_importFiles = array();
		$filename   =  realpath($filename);
		if (!isset($_importFiles[$filename])) {
			if($this->file_exists_case($filename)){
				require $filename;
				$_importFiles[$filename] = true;
			}
			else
			{
				$_importFiles[$filename] = false;
			}
		}
		return $_importFiles[$filename];
	}
	
	/**
	 * 区分大小写的文件存在判断
	 */
	private function file_exists_case($filename) {
		$is_win = strstr(PHP_OS, 'WIN') ? 1 : 0;
		if(is_file($filename)) {
			if($is_win) {
				if(basename(realpath($filename)) != basename($filename))
					return false;
			}
			return true;
		}
		return false;
	}
	
	/**
	 * 私有克隆函数，防止对象复制
	 */
	private function __clone() {
		throwException('Clone not allowed!');
	}
}